package com.sharemarking.application.common.controller;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.channels.FileChannel;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;

import com.sharemarking.application.common.BaseSpecification;
import com.sharemarking.application.common.entity.Album;
import com.sharemarking.application.common.entity.Attach;
import com.sharemarking.application.common.exception.ImageNotFoundException;
import com.sharemarking.application.common.repository.AlbumRepo;
import com.sharemarking.application.common.repository.AttachRepo;
import com.sharemarking.application.common.view.AttachView;

import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;

/**
 * 文件上传控制器,实现秒传，下载断线重连，断点上传<br><br>
 * 文件目录规划<br>
 * >upload<br>
 * >>>attaches>{YYYYMM} >{member.id_md5}<br>
 * >>>images  >{YYYYMM} >{member.id_md5}<br>
 * >>>chunks  >{YYYYMM} >{member.id_md5} >{chunk}<br>
 * 附件（即attaches与chunks文件夹下的文件）不带扩展名，内容为文件字节流，只需要更改扩展名即可使用。
 * 如果附件需要加密，则对字节流加密后保存，取出时再解密即可。<br>
 * \\upload\\chunks 除了当前月份外的数据均可以删除。<br>
 * 由于限制图片最大为5M,暂不作秒传优化
 * @author song
 */
@Controller
@RequestMapping("file")
public class FileController {
	
	private Logger logger=LoggerFactory.getLogger(this.getClass());
	
	@Value("${spring.http.multipart.location}") 
	private String location;
	/**
	 * 附件上传目录
	 */
	private File attachesFolder;
	/**
	 * 附件分片上传临时目录，该文件夹下的文件应定时清除
	 */
	private File chunksFolder;
	/**
	 * 图片原文件上传目录
	 */
	private File albumsFolder;
	/**
	 * 缩略图上传目录
	 */
	private File thumbsFolder;
	
	@Autowired
	private AttachRepo ar;
	
	@Autowired
	private AlbumRepo ir;
	
	@PostConstruct
	public void mkBaseDirs(){
		attachesFolder=new File(location,"attaches");
		if(!attachesFolder.exists()){
			attachesFolder.mkdir();
			logger.info("已创建文件夹：%s",attachesFolder.getPath());
		}
		chunksFolder=new File(location,"chunks");
		if(!chunksFolder.exists()){
			chunksFolder.mkdir();
			logger.info("已创建文件夹：%s",chunksFolder.getPath());
		}
		albumsFolder=new File(location,"albums");
		if(!albumsFolder.exists()){
			albumsFolder.mkdir();
			logger.info("已创建文件夹：%s",albumsFolder.getPath());
		}
		thumbsFolder=new File(location,"thumbs");
		if(!thumbsFolder.exists()){
			thumbsFolder.mkdir();
			logger.info("已创建文件夹：%s",albumsFolder.getPath());
		}
	}
	
	@RequestMapping("test")
	public void test(){
		//UserDetailImpl user=(UserDetailImpl)auth.getPrincipal();
		//System.out.println(user.getId());
	}
	
	@SuppressWarnings("resource")
	@RequestMapping(value="attach",method=RequestMethod.POST)
	@ResponseBody
	public Map<String,Object> attachPost(MultipartFile file,String md5,
			Integer chunks,Integer chunk) throws IOException{
		if (file.isEmpty() || StringUtils.isEmpty(md5))return null;
		Integer attachId=null;
		Calendar now=Calendar.getInstance();
		if(chunk==null){
			String folder=String.format("%4d%02d", now.get(Calendar.YEAR),now.get(Calendar.MONTH)+1);
			File dir=new File(attachesFolder,folder);
			if(!dir.exists())dir.mkdir();
			String relativePath=new File(folder,md5).getPath();
			File target=new File(attachesFolder,relativePath);
			file.transferTo(target);
			Attach attach=new Attach();
			attach.setName(file.getOriginalFilename());
			attach.setMd5(md5);
			attach.setSize(file.getSize());
			attach.setContentType(file.getContentType());
			attach.setPath(relativePath);
			ar.save(attach);
			attachId=attach.getId();
		}else{
			if( chunks==null || chunk>chunks)return null;
			File chunkMonthDir=new File(chunksFolder,String.format("%4d%02d", now.get(Calendar.YEAR),now.get(Calendar.MONTH)+1));
			if(!chunkMonthDir.exists())chunkMonthDir.mkdir();
			File chunkFileDir=new File(chunkMonthDir,md5);
			if(!chunkFileDir.exists())chunkFileDir.mkdir();
			File target=new File(chunkFileDir,Integer.toString(chunk));
			file.transferTo(target);
			if(chunk==chunks-1){
				String folder=String.format("%4d%02d", now.get(Calendar.YEAR),now.get(Calendar.MONTH)+1);
				File finalDir=new File(attachesFolder,folder);
				if(!finalDir.exists())finalDir.mkdir();
				File finalFile=new File(finalDir,md5);
				FileChannel outChannel = new FileOutputStream(finalFile).getChannel();
				File chunkDir=new File(chunksFolder,String.format("%4d%02d%s%s", now.get(Calendar.YEAR),now.get(Calendar.MONTH)+1,File.separator,md5));
				//File preChunkDir=new File(chunksFolder,String.format("%4d%02d%s%s", now.get(Calendar.YEAR),now.get(Calendar.MONTH),File.separator,fileSymbol));
	            FileChannel inChannel;
	            for(int i=0;i<chunks;i++){
	            	File chunkFile=new File(chunkDir,Integer.toString(i));
	            	//这个月的文件夹里找不到，则到上个月找,小概率事件，不处理
	            	//if(!chunkFile.exists())chunkFile=new File(preChunkDir,Integer.toString(i));
	            	inChannel = new FileInputStream(chunkFile).getChannel();
	            	inChannel.transferTo(0, inChannel.size(), outChannel);
	            	inChannel.close();
	            }
	            outChannel.close();
	            String relativePath=new File(folder,md5).getPath();
	            Attach attach=new Attach();
				attach.setName(file.getOriginalFilename());
				attach.setMd5(md5);
				attach.setSize(finalFile.length());
				attach.setContentType(file.getContentType());
				attach.setPath(relativePath);
				ar.save(attach);
				//删除文件，建议手动删除
				//chunkFileDir.delete();
				attachId=attach.getId();
			}
		}
		Map<String,Object> map=new HashMap<String,Object>();
		map.put("id", attachId);
		return map;
	}
	/**
	 * 下载附件，支持大文件下载断线重连(待测试）
	 * TODO 加上member权限验证
	 * @param id
	 * @return
	 * @throws UnsupportedEncodingException 
	 */
	@RequestMapping(value="attach/{id}",method=RequestMethod.GET)
	public AttachView attachGet(@PathVariable("id")Integer id,Model model) throws UnsupportedEncodingException{
		Attach attach=ar.findOne(id);
		if(attach==null)return null;
		model.addAttribute("file",new File(attachesFolder,attach.getPath()));
		String fileName=new String(attach.getName().getBytes("UTF-8"),"iso-8859-1");//解决中文名称乱码问题
		model.addAttribute("attachment", fileName);
		//model.addAttribute("contentType", MediaType.APPLICATION_OCTET_STREAM_VALUE);//默认值
		return new AttachView();
	}
	/**
	 * 如果存在文件，则直接返回attach.id
	 * @param md5
	 * @param size
	 * @return
	 */
	@RequestMapping(value="checkFile",method=RequestMethod.GET)
	@ResponseBody
	public Map<String,Object> checkFile(String md5){
		Map<String,Object> map=new HashMap<String,Object>();
		List<Attach> list=ar.findAllByMd5(md5);
		map.put("exists", list.size()>0);
		if(list.size()>0)map.put("id", list.get(0).getId());
		return map;
	}
	
	/**
	 * 检测文件chunk是否存在（用于断点续传）
	 * @param md5
	 * @param size
	 * @param chunk
	 * @return
	 */
	@RequestMapping(value="checkChunk",method=RequestMethod.GET)
	@ResponseBody
	public Map<String,Boolean> checkChunk(String md5,Integer chunk){
		Map<String,Boolean> map=new HashMap<String,Boolean>();
		Calendar now=Calendar.getInstance();
		File chunkDir=new File(chunksFolder,String.format("%4d%02d%s%s", now.get(Calendar.YEAR),now.get(Calendar.MONTH)+1,File.separator,md5));
		boolean exists=new File(chunkDir,Integer.toString(chunk)).exists();
		map.put("exists", exists);
		return map;
	}
	/**
	 * 附件列表页
	 * @param e
	 * @param page
	 * @param size
	 * @param model
	 */
	@RequestMapping(value="attaches",method=RequestMethod.GET)
	public void attaches(Attach e,Integer page,Integer size,Model model){
		if(page==null)page=0;
		else page-=1;
		if(size==null)size=30;
		PageRequest req=new PageRequest(page,size);
		Page<Attach> p=ar.findAll(new BaseSpecification<Attach>(e), req);
		model.addAttribute("page", p);
		model.addAttribute("search", e);
	}
	/**
	 * 新增附件页面
	 */
	@RequestMapping(value="attachesAdd",method=RequestMethod.GET)
	public void attachesAdd(){
	}
	/**
	 * 创建图片
	 * @param authentication
	 * @param file
	 * @param md5
	 * @throws IOException
	 */
	@RequestMapping(value="album",method=RequestMethod.POST)
	@ResponseBody
	public Map<String,Object> album(MultipartFile file,String md5)throws IOException{
		if(file.isEmpty() || StringUtils.isEmpty(md5))return null;
		Calendar now=Calendar.getInstance();
		String folder=String.format("%4d%02d", now.get(Calendar.YEAR),now.get(Calendar.MONTH)+1);
		File dir=new File(albumsFolder,folder);
		if(!dir.exists())dir.mkdir();
		String relativePath=new File(folder,md5).getPath();
		File target=new File(albumsFolder,relativePath);
		file.transferTo(target);
		BufferedImage image = ImageIO.read(target);
		int width=image.getWidth();
		int height=image.getHeight();
		int min=width<height?width:height;
		File thumb=new File(thumbsFolder,folder);
		Thumbnails.of(image).sourceRegion(Positions.CENTER, min, min).size(100, 100)
			.outputQuality(1f).outputFormat("jpg").toFile(thumb);
		Album album=new Album();
		album.setThumbPath(relativePath+".jpg");
		album.setOriginPath(relativePath);
		album.setName(file.getOriginalFilename());
		album.setMd5(md5);
		album.setContentType(file.getContentType());
		album.setSize(file.getSize());
		ir.save(album);
		Map<String,Object> map=new HashMap<String,Object>();
		map.put("id", album.getId());
		return map;
	}
	
	/**
	 * 获取图片
	 * TODO 授权
	 * @param id
	 * @return
	 * @throws UnsupportedEncodingException 
	 * @throws ImageNotFoundException 
	 */
	@RequestMapping(value="album/{id}",method=RequestMethod.GET)
	public AttachView albumGet(@PathVariable("id")Integer id,Boolean isThumb,Model model) throws UnsupportedEncodingException, ImageNotFoundException{
		Album album=ir.findOne(id);
		if(album==null){
			throw new ImageNotFoundException();
		}else{
			if(Boolean.TRUE.equals(isThumb))model.addAttribute("file",new File(thumbsFolder,album.getThumbPath()));
			else model.addAttribute("file",new File(albumsFolder,album.getOriginPath()));
			String fileName=new String(album.getName().getBytes("UTF-8"),"iso-8859-1");//解决中文名称乱码问题
			model.addAttribute("attachment", fileName);
			model.addAttribute("contentType", album.getContentType());//默认值
		}
		return new AttachView();
	}
	/**
	 * 相册列表页
	 * @param e
	 * @param page
	 * @param size
	 * @param model
	 */
	@RequestMapping(value="albums",method=RequestMethod.GET)
	public void albums(Album e,Integer page,Integer size,Model model){
		if(page==null)page=0;
		else page-=1;
		if(size==null)size=30;
		PageRequest req=new PageRequest(page,size);
		Page<Album> p=ir.findAll(new BaseSpecification<Album>(e), req);
		model.addAttribute("page", p);
		model.addAttribute("search", e);
	}
	/**
	 * 删除相册中的图片
	 * TODO 授权
	 * @param id
	 * @return
	 */
	@RequestMapping(value="album/{id}/delete",method=RequestMethod.GET)
	@Transactional
	public String albumDelete(@PathVariable("id")Integer id){
		ir.findOne(id).setDelFlag(true);
		return "redirect:/file/albums";
	}
	/**
	 * 新增相片页面
	 */
	@RequestMapping(value="albumsAdd",method=RequestMethod.GET)
	public void albumsAdd(){
		
	}
	/**
	 * 表单中使用的选择图片
	 */
	@RequestMapping(value="albumsSelecter",method=RequestMethod.GET)
	public void albumsSelecter(Model model){
		List<Album> list=ir.findAllByDelFlag(Boolean.FALSE);
		model.addAttribute("albumList", list);
	}
	/**
	 * 图表预览页面
	 */
	@RequestMapping(value="albumsView",method=RequestMethod.GET)
	public void albumsView(){
	}
	
	@ExceptionHandler
	@ResponseStatus(value=HttpStatus.NOT_FOUND)
	public void imageNotFountException(ImageNotFoundException infe){

	}
}
